
#include <BImage>
#include <BState>

#include "ReadWrite_obj.h"

ReadWrite_obj::ReadWrite_obj()
{
	_author = "zym";
	_version = "1.0";
	_description = "This is BWE Plugin for reading *.jpg image file, write width libjpeg, thank a lot !";
}
ReadWrite_obj::~ReadWrite_obj()
{

}

bool ReadWrite_obj::readObject(const BString& fileName, BObject* object)
{
	_scene = dynamic_cast<BScene*>(object);
	if (!_scene)
		return false;

	_fileName = fileName;
	_mates.clear();
	_vs.clear();
	_vns.clear();
	_vts.clear();
	_group.clear();

	BFile file(fileName);
	if(!file.open(fileName, BFile::Mode_Read))
		return false;
	
	BString name = fileName.name();
	name.remove(".obj");
	_scene->setName(name);

	BState* state = new BState();
	state->setLighting(true);
	state->setCullBack(false);

	BString geoName;
	BString mtlfile;
	BString text;
	int line = 0;
	while (file.readLine(text))
	{
		line++;
		text.trimmed();
		if (text.empty())
			continue;
		if (text.beginsWidth("v "))
		{
			BVector vector;
			if (convertTextToVector(text, 2, vector))
			{
				_vs.append(vector);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				return false;
			}
			continue;
		}
		if (text.beginsWidth("vn "))
		{
			BVector normal;
			if (convertTextToVector(text, 3, normal))
			{
				_vns.append(normal);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				return false;
			}
			continue;
		}
		if (text.beginsWidth("vt "))
		{
			BCoord coord;
			if (convertTextToCoord(text, 3, coord))
			{
				_vts.append(coord);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				return false;
			}
			continue;
		}
		if (text.beginsWidth("f "))
		{
			if (!readFace(text, 2))
			{
				printf("Error --- read face failed : %s\n", text.cstr());
				return false;
			}
			continue;
		}
		if (text.beginsWidth("o "))
		{
			makeGeometry();

			BString nodeName;
			text.substr(nodeName, 2);
			nodeName.trimmed();
			_node = new BNode();
			_node->setName(nodeName);
			_node->setState(state);
			_scene->inserts(_node);
			continue;
		}
		if (text.beginsWidth("g "))
		{
			makeGeometry();

			text.substr(geoName, 2);
			geoName.trimmed();
			continue;
		}
		if (text.beginsWidth("usemtl "))
		{
			BString mateName;
			text.substr(mateName, 7);
			mateName.trimmed();
			MateObj* mate = findMaterial(mateName);
			_material = mate->material;
			_texture = mate->texture;
			continue;
		}
		if (text.beginsWidth("s "))
		{
			makeGeometry();

			BString groupStr;
			text.substr(groupStr, 2);
			int sid = groupStr.toInt();

			_geometry = new BGeometry;
			_geometry->setName(geoName + groupStr);
			continue;
		}
		if (text.beginsWidth("mtllib"))
		{
			if (!text.substr(mtlfile, 6))
				return false;
			mtlfile.trimmed();
			if (!loadMtllib(mtlfile))
			{
				printf("%d load mtllib failed : %s\n", line, mtlfile.cstr());
			}
			continue;
		}
		if (text[0] == '#')
		{
			continue;
		}
	}

	makeGeometry();

	return true;
}
bool ReadWrite_obj::writeObject(const BString& fileName, const BObject* object)
{
	return false;
}

const BString& ReadWrite_obj::author() const
{
	return _author;
}
const BString& ReadWrite_obj::version() const
{
	return _version;
}
const BString& ReadWrite_obj::description() const
{
	return _description;
}
const BString& ReadWrite_obj::log() const
{
	return _log;
}

void ReadWrite_obj::getIllum(int illum)
{
	if (illum == 0)//Color on and Ambient off
		return;
	if (illum == 1)// Color on and Ambient on
		return;
	if (illum == 2)// Highlight on
		return;
	if (illum == 3)// Reflection on and Ray trace on
		return;
	if (illum == 4)// Transparency: Glass on
		return;
	//Reflection : Ray trace on
	if (illum == 5)// Reflection : Fresnel on and Ray trace on
		return;
	if (illum == 6)// Transparency : Refraction on
		return;
	//Reflection : Fresnel off and Ray trace on
	if (illum == 7)// Transparency : Refraction on
		return;
	//Reflection : Fresnel on and Ray trace on
	if (illum == 8)// Reflection on and Ray trace off
		return;
	return;
	if (illum == 9)// Transparency : Glass on
		return;
	//Reflection : Ray trace off
	if (illum == 10)// Casts shadows onto invisible surfaces
		return;
}
bool ReadWrite_obj::convertTextToVector(const BString& text, int pos, BVector& vector)
{
	pos = text.word(_word, pos);
	if (pos == text.size())
		return false;
	vector.x() = _word.toFloat();

	pos = text.word(_word, pos);
	if (pos == text.size())
		return false;
	vector.y() = _word.toFloat();

	pos = text.word(_word, pos);
	if (_word.empty())
		return false;
	vector.z() = _word.toFloat();

	return true;
}
bool ReadWrite_obj::convertTextToCoord(const BString& text, int pos, BCoord& coord)
{
	pos = text.word(_word, pos);
	if (pos == text.size())
		return false;
	coord.x() = _word.toFloat();

	pos = text.word(_word, pos);
	if (_word.empty())
		return false;
	coord.y() = _word.toFloat();

	return true;
}
bool ReadWrite_obj::convertTextToColor(const BString& text, int pos, BColor& color)
{
	pos = text.word(_word, pos);
	if (pos == text.size())
	{
		BByte c = _word.toFloat() * 255;
		color.r() = c;
		color.g() = c;
		color.b() = c;
		return true;
	}
	color.r() = _word.toFloat() * 255;

	pos = text.word(_word, pos);
	if (pos == text.size())
		return false;
	color.g() = _word.toFloat() * 255;

	pos = text.word(_word, pos);
	if (_word.empty())
		return false;
	color.b() = _word.toFloat() * 255;

	return true;
}

bool ReadWrite_obj::readFace(const BString& text, int pos)
{
	int begin = _group.size();
	int count = 0;
	VertInfo vinfo;
	while (pos < text.size())
	{
		int seg_end = text.find(' ', pos);

		text.substr(_segment, pos, seg_end);
		pos = seg_end + 1;

		_segment.trimmed();
		pos = seg_end + 1;
		if (_segment.empty())
			continue;

		int begin = 0;
		int end = 0;

		//vertex index
		end = _segment.find('/', begin);
		if (end == begin)
		{
			printf("Invalid segment : %s", _segment.cstr());
			return false;
		}
		_segment.substr(_strIndex, begin, end);
		vinfo.iv = _strIndex.toInt() - 1;

		//texcoord index
		begin = end + 1;
		end = _segment.find('/', begin);
		if (end == begin)
			continue;
		_segment.substr(_strIndex, begin, end);
		vinfo.ivt = _strIndex.toInt() - 1;

		//normal index
		begin = end + 1;
		end = _segment.find('/', begin);
		if (end == begin)
			continue;
		_segment.substr(_strIndex, begin, end);
		vinfo.ivn = _strIndex.toInt() - 1;

		_group.append(vinfo);
		count++;
	}

	Primitive primitive = Primitive_None;
	if (count < 3)
		return false;

	if (count == 3)
		primitive = Primitive_Triangles;

	if (count == 4)
		primitive = Primitive_Quads;

	if (count > 4)
		primitive = Primitive_Polygon;

	if (!_element || _element->primitive() != primitive)
	{
		_element = new BElement(primitive);
		_geometry->addElement(_element);

		BIndexArray* indices = new BIndexArray();
		_element->setIndices(indices);
	}
	BIndexArray* indices = _element->indices();
	for (int i = 0; i < count; i++)
	{
		const VertInfo& info = _group[begin + i];
		indices->append(info.iv);
	}
	return true;
}

void ReadWrite_obj::makeGeometry()
{
	if (_group.empty())
		return;

	BMap<int, VertInfo> ivmap;

	for (int i = 0; i < _group.size(); i++)
	{
		VertInfo& info = _group[i];
		ivmap[info.iv] = info;
	}

	int numVert = ivmap.size();
	BVectorArray* vertices = new BVectorArray(numVert);
	BVectorArray* normals = new BVectorArray(numVert);
	BCoordArray* texCoords = new BCoordArray(numVert);

	int index = 0;
	for (auto it = ivmap.begin(); it.valid(); it++)
	{
		VertInfo& vert = *it;
		(*vertices)[index] = _vs[vert.iv];
		(*normals)[index] = _vns[vert.ivn];
		(*texCoords)[index] = _vts[vert.ivt];
		vert.iv = index;
		index++;
	}

	for (int i = 0; i < _geometry->elementCount(); i++)
	{
		BElement* element = _geometry->element(i);
		BIndexArray* indices = element->indices();
		if (indices)
		{
			for (int i = 0; i < indices->size(); i++)
			{
				int index_old = (*indices)[i];
				int index_new = ivmap[index_old].iv;
				(*indices)[i] = index_new;
			}
		}
	}
	_geometry->setVertices(vertices);
	_geometry->setNormals(normals);
	_geometry->setTexCoords(texCoords);
	_geometry->setMaterial(_material);
	_geometry->setTexture(_texture);
	if (_node)
	{
		_node->addShape(_geometry);
	}
	else
	{
		_scene->inserts(_geometry);
	}
	_group.reset();
	_geometry = 0;
	_element = 0;
}

bool ReadWrite_obj::loadMtllib(const BString& mtlfile)
{
	BString mtlFileName = _fileName.path();
	mtlFileName << '/';
	mtlFileName << mtlfile;
	BFile file(mtlFileName);
	if (!file.open(BFile::Mode_Read))
		return false;

	BByte factor = 255;
	MateObj* mate = 0;
	BString mtlName;
	BString text;
	int line = 0;
	while (file.readLine(text))
	{
		text.trimmed();
		if (text.empty())
			continue;
		if (text[0] == '#')
			continue;
		if (text.beginsWidth("newmtl "))
		{
			if (!text.substr(mtlName, 6))
				return false;
			mtlName.trimmed();
			mate = new MateObj();
			mate->material = new BMaterial();
			mate->material->setName(mtlName);
			_mates.append(mate);
			continue;
		}
		if (text.beginsWidth("illum "))
		{
			BString illumStr;
			text.substr(illumStr, 6);
			int illum = illumStr.toInt();
			getIllum(illum);
			continue;
		}
		if (text.beginsWidth("Ka "))
		{
			BColor ambient = mate->material->ambient();
			ambient.a() = factor;
			if (!convertTextToColor(text, 3, ambient))
				return false;
			mate->material->setAmbient(ambient);
			continue;
		}
		if (text.beginsWidth("Kd "))
		{
			BColor diffuse = mate->material->diffuse();
			diffuse.a() = factor;
			if (!convertTextToColor(text, 3, diffuse))
				return false;
			mate->material->setDiffuse(diffuse);
			continue;
		}
		if (text.beginsWidth("Ks "))
		{
			BColor specular = mate->material->specular();
			specular.a() = factor;
			if (!convertTextToColor(text, 3, specular))
				return false;
			mate->material->setSpecular(specular);
			continue;
		}
		if (text.beginsWidth("Ke "))
		{
			BColor emission = mate->material->emission();
			emission.a() = factor;
			if (!convertTextToColor(text, 3, emission))
				return false;
			mate->material->setEmission(emission);
			continue;
		}
		if (text.beginsWidth("map_Ka "))
		{
			BString fileName;
			text.substr(fileName, 7);
			fileName.trimmed();
			continue;
		}
		if (text.beginsWidth("map_Kd "))
		{
			BString texName;
			text.substr(texName, 7);
			texName.trimmed();
			BString fileName = _fileName.path();
			fileName << '/' << texName;
			BImage* image = Read<BImage>(fileName);
			if (!image)
			{
				printf("Error --- read texture file failed : %s\n", texName.cstr());
			}
			if (image)
			{
				image->setFileName(texName);
				image->setBlend(BTexture::Blend_Modulate);
			}
			mate->texture = image;
			continue;
		}	
		if (text.beginsWidth("Ns "))
		{
			BString exponentStr;
			text.substr(exponentStr, 3);
			float exponent = exponentStr.toFloat();
			continue;
		}
		if (text.beginsWidth("Ni "))
		{
			BString pticalStr;
			text.substr(pticalStr, 3);
			float ptical = pticalStr.toFloat();
			continue;
		}
		if (text.beginsWidth("d "))
		{
			BString factorStr;
			text.substr(factorStr, 2);
			factor = factorStr.toFloat() * 255;
			continue;
		}
		if (text.beginsWidth("Tr "))
		{
			continue;
		}
		if (text.beginsWidth("Tf "))
		{
			continue;
		}
	}
	return true;
}

MateObj* ReadWrite_obj::findMaterial(const BString& matName)
{
	for(BList<MateObjHolder>::iterator it = _mates.begin(); it != _mates.end(); ++it)
	{
		MateObj* mate = it->ptr();
		BMaterial* material = mate->material.ptr();
		if(material->name() == matName)
			return mate;
	}
	return 0;
}


